
import * as THREE from '../build/three.module.js';
const defaultValue = (a, b) => {
    if (a === null || a === undefined) return b;
    return a;
}

import * as TWEEN from "../extensions/tween.module.js";
import { Line2 } from '../examples/jsm/lines/Line2.js';
import { LineMaterial } from '../examples/jsm/lines/LineMaterial.js';
import { LineGeometry } from '../examples/jsm/lines/LineGeometry.js';
import {
    CSS3DObject,
    CSS3DSprite
} from "../costum/CssRender.js";

import {
    FBXLoader
} from '../examples/jsm/loaders/FBXLoader.js';

import { viewData } from "./viewData.js"

const initView = {
    position: [],
    target: []
};

const fbxloader = new FBXLoader();

export class Logic {
    constructor(camera, scene, controls, options = {}) {
        this.camera = camera;
        this.scene = scene;
        this.controls = this.controller = controls;
        this.lines = [];
        this.points = [];
        this.labels = [];

        // 关于相机飞行的控制
        this.cameraFlyingTween = undefined;
        this.cameraInUse = false;

        this.textChangedCallback = options.textChangedCallback;
        this.modelUrl = options.modelUrl;

        // 当前场景 id
        this.sceneModelId = null;
        this.lastObjectInfo = null;

        // 当前场景的类型
        this.sceneType = 1;

        this.initSceneData();
    }

    initSceneData() {
        const { scene, lines, points, labels } = this;

        if (!scene.getObjectByName("scene-model-container")) {
            const group = this.group = new THREE.Group();
            group.name = "scene-model-container";
            scene.add(group);
        }

        // const matLine = new LineMaterial({
        //     color: 0xb6630d,
        //     linewidth: 2,
        //     vertexColors: false,
        //     dashed: false,
        //     alphaToCoverage: true,
        //     depthTest: false,
        // });

        const matLine = new THREE.LineBasicMaterial({
            color: 0xb6630d,
            depthTest: false
        });


        const pointMaterial = new THREE.PointsMaterial({
            color: 0xb6630d,
            size: 1,
            depthTest: false,
            sizeAttenuation: true
        });

        new Array(4).fill(0).forEach((value, index) => {
            const elementId = index + 1;

            // const geometry = new LineGeometry();
            // geometry.setPositions([0, 0, 0, 1, 1, 1]);
            const geometry = new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(), new THREE.Vector3()])

            const line = new THREE.Line(geometry, matLine);
            // line.computeLineDistances();
            // line.scale.set(1, 1, 1);
            line.renderOrder = 2;

            line.visible = false;
            line.elementId = elementId;

            lines.push(line);

            // 创建点
            const pointGeometry = new THREE.BufferGeometry();
            pointGeometry.setFromPoints([new THREE.Vector3(0, 0, 0)]);

            const point = new THREE.Points(pointGeometry, pointMaterial);
            point.renderOrder = 2;
            point.elementId = elementId;
            point.visible = false;

            points.push(point);

            const label = new CSS3DSprite();
            label.elementId = elementId;
            label.visible = false;

            label.position.set(0, 0, 0);
            label.scale.set(1, 1, 1);

            labels.push(label);
        });

    }

    async loadModel(id = "1") {
        // ${this.modelUrl}
        const url = `${this.modelUrl}/fire-model/${id}.fbx`;

        return new Promise((resolve) => {
            fbxloader.load(url, (fbx) => {
                fbx.traverse(node => {
                    if (node.isMesh) {
                        // console.log(node.material)
                        node.material.emissive = node.material.color;
                        node.material.emissiveIntensity = 1;
                        node.material.emissiveMap = node.material.map;
                        node.material.side = THREE.DoubleSide;
                    }
                });
                resolve(fbx);
            }, () => { }, error => {
                console.log(error);
            });
        });
    }

    async loadData(type, objectInfo) {

        if (objectInfo) {
            this.lastObjectInfo = objectInfo;
        }

        const loaded = !objectInfo;

        objectInfo = this.lastObjectInfo;

        if (!objectInfo) return;

        const {
            effectiveStatus,
            introduceId,
            name,
            sort,
            useExplain,
            smExhibitsIntroduceDetailList
        } = objectInfo;

        const id = introduceId;

        const { defaultView, objectList } = viewData;
        const { scene } = this;

        if (!loaded) {
            this.group.clear();

            // 加载模型
            const model = await this.loadModel(id);
			
			setTimeout(() => {
				uni.hideLoading()
			}, 100)

            this.group.add(model);
        }

        this.setData(type, id, smExhibitsIntroduceDetailList);
    }

    async setData(type, id, smExhibitsIntroduceDetailList) {
        this.clear();

        // 根据解析
        const model = this.group.children[0];

        smExhibitsIntroduceDetailList.forEach(async (meshInfo, i) => {
            const {
                id,
                introduceId,
                materialExplain,
                modelId,
                modelName,
                sort
            } = meshInfo;

            // 获取mesh 设置初始化状态
            const mesh = model.getObjectByName(modelId);
            const index = i;
            if (mesh) {
                if (!mesh.userData.originPosition) {
                    mesh.userData.originPosition = mesh.position.clone();
                }

                const meshView = this.findTargetInfo(introduceId, modelId);
                if (!meshView) return;

                const sceneType = this.sceneType;
                let moveVector = new THREE.Vector3().fromArray(meshView.moved);
                if (type === 1 || type === 2) {
                    if (sceneType === 3) {
                        this.clearByIndex(index);
                        this.shrinkMesh(mesh, mesh.userData.originPosition.clone(), 2000);
                    }
                } else if (type === 3) {
                    if (sceneType !== 3) {
                        await this.expandMesh(mesh, mesh.userData.originPosition.clone().add(moveVector), 2000);
                        this.setDataByIndex(mesh, index, meshView, materialExplain);
                    }
                }
            }

        });

        // 设置初始化位置
        const viewData = this.findView(id);

        this.sceneType = type;

        let position = new THREE.Vector3(5, 0, 5);
        let target = new THREE.Vector3(0, 0, 0);

        if (viewData && viewData.view) {
            if (viewData.view.position)
                position.fromArray(viewData.view.position);
            if (viewData.view.target)
                target.fromArray(viewData.view.target);
            if (type === 3 && viewData.detailView) {
                if (viewData.detailView.position) {
                    position.fromArray(viewData.detailView.position);
                }
                if (viewData.detailView.target) {
                    target.fromArray(viewData.detailView.target);
                }
            }
        }

        await this.moveCamera(position, target, 2000);
    }

    expandMesh(mesh, position, time = 2000) {
        return this.moveMesh(mesh, position, time);
    }

    shrinkMesh(mesh, position, time = 2000) {
        return this.moveMesh(mesh, position, time);
    }

    setDataByIndex(mesh, index, data = {}, materialExplain = "") {
        const {
            start = [0, 0, 0],
            end = [0, 0, 0],
            scale = [0.25, 0.25, 0.25]
            // scale = [0.01, 0.01, 0.01]
        } = data;

        // setPoint 
        this.setPointByIndex(mesh, index, start);

        // setLine
        this.setLineByIndex(mesh, index, start, end);

        // set label 
        this.setLabelByIndex(mesh, index, end, materialExplain, scale);

    }

    findView(id) {
        return viewData.objectList.find(item => { return item.id === id; });
    }

    findTargetInfo(id, modelId) {
        const view = viewData.objectList.find(item => { return item.id === id; });

        if (view.children) {
            return view.children.find(item => { return item.id === modelId; });
        }
    }

    setLineByIndex(mesh, index, start, end) {
        const line = this.lines[index];
        line.geometry.setFromPoints([
            new THREE.Vector3().fromArray(start),
            new THREE.Vector3().fromArray(end)
        ]);

        // line.computeLineDistances();
        line.visible = true;

        if (mesh.children.findIndex(child => { return child === line }) === -1) {
            mesh.add(line);
        }
    }

    setPointByIndex(mesh, index, position) {
        const point = this.points[index];
        point.geometry.setFromPoints([new THREE.Vector3().fromArray(position)]);

        point.visible = true;

        if (mesh.children.findIndex(child => { return child === point }) === -1) {
            mesh.add(point);
        }
    }

    setLabelByIndex(mesh, index, position, text, scale = [1, 1, 1]) {
        const label = this.labels[index];

        label.position.set(...position);
        label.scale.set(...scale);
        label.visible = true;

        if (mesh.children.findIndex(child => { return child === label }) === -1) {
            mesh.add(label);
        }

        this.textChangedCallback && this.textChangedCallback(label, index + 1, text);
    }

    clear() {
        const { labels, lines, points } = this;

        lines.forEach(line => {
            line.visible = false;
        });

        points.forEach(point => {
            point.visible = false;
        });

        labels.forEach((label, index) => {
            label.visible = false;
            this.textChangedCallback && this.textChangedCallback(labels[index], index, "");
        });
    }

    clearByIndex(index) {
        const { labels, lines, points } = this;
        labels[index].visible = false;


        lines[index].visible = false;
        points[index].visible = false;
    }

    onTick() {
        TWEEN.update();
    }

    /**
     * 设置相机飞行到新的位置
     * @param {THREE.Vector3} newP 飞行到相机的位置
     * @param {THREE.Vector3} newT 飞行到相机朝向的位置
     * @param {Number} time 飞行的总时长，单位是毫秒
     * @param {Function} callback 飞行成功或者被取消后的回调
     * @param {Boolean} force 是否取消相机的飞行
     * @param {Object}  options  适配相机飞行用配置
     * @param {String}  options.easing  过渡方式，默认cubic
     * @param {Boolean} options.instantStart 是否立即执行，默认为true
     * @param {Boolean} options.usedInChain 是否在链式飞行中，默认为false
     * @param {Number}  options.delay   执行前延迟时间，默认为undefined，单位毫秒
     * @param {Object}  options.presetOldP  预设的起点相机位置，默认undefined
     * @param {Object}  options.presetOldT  预设的起点相机朝向，默认undefined
     */
    _moveCamera(newP, newT, time, callback, force = true, options = {}) {
        let {
            easing = "cubic",
            instantStart = true,
            usedInChain = false,
            delay,
            presetOldP,
            presetOldT,
            customOnUpdate
        } = options

        if (force && this.cameraInUse && instantStart) {
            this.cameraFlyingTween.stop();
            this.cameraFlyingTween._forceStop = true;
            this.cameraFlyingTween._onCompleteCallback();
        }

        let oldP = presetOldP === undefined ? this.camera.position : presetOldP;
        let oldT = presetOldT === undefined ? this.controller.target : presetOldT;

        time = defaultValue(time, 2000);

        let tween = new TWEEN.Tween({
            x1: oldP.x,
            y1: oldP.y,
            z1: oldP.z,
            x2: oldT.x,
            y2: oldT.y,
            z2: oldT.z,
        });

        tween.to(
            {
                x1: newP.x,
                y1: newP.y,
                z1: newP.z,
                x2: newT.x,
                y2: newT.y,
                z2: newT.z,
            },
            time,
        );

        if (delay !== undefined)
            tween.delay(delay)

        // 每一帧执行函数
        tween.onUpdate((object) => {
            if (!this.stopCameraMove) {

                this.camera.position.set(object.x1, object.y1, object.z1);

                this.controller.target.x = object.x2;
                this.controller.target.y = object.y2;
                this.controller.target.z = object.z2;

                this.controller.update();

                customOnUpdate && customOnUpdate(object)
            }
        });

        // 动画完成后的执行函数
        tween.onComplete(() => {
            this.controller.enabled = true;
            // 删除掉Tween
            if (!usedInChain && this.cameraFlyingTween)
                TWEEN.remove(this.cameraFlyingTween);
            this.cameraFlyingTween = undefined;
            this.cameraInUse = false;

            callback && callback(tween._forceStop);
        });

        switch (easing) {
            case "cubic":
                tween.easing(TWEEN.Easing.Cubic.InOut);
                break;
            case "linear":
                tween.easing(TWEEN.Easing.Linear.None);
                break;
            default:
                tween.easing(TWEEN.Easing.Linear.None);
                break;
        }

        this.controller.enabled = false;

        // 这个函数必须有、这个是启动函数、不加不能启动
        if (instantStart)
            tween.start();

        this.cameraFlyingTween = tween;
        this.cameraInUse = true;

        return tween
    };

    moveCamera(newP, newT, time, force = true, options = {}) {
        return new Promise((resolve, reject) => {
            this._moveCamera(newP, newT, time, () => {
                resolve();
            }, force = true, options = {});
        });
    }

    setResolution(width, height) {
        // this.lines.forEach(line => {
        //     line.material.uniforms.resolution.value.set(1 / width, 1 / height);
        //     line.material.resolution.set(1 / width, 1 / height);
        // });
    }

    /**
     * mesh移动的动画
     * @param mesh
     * @param moveVector
     * @param time
     * @param callback
     */
    _moveMesh(mesh, targetVector, time, callback) {
        let oldP = { x: mesh.position.x, y: mesh.position.y, z: mesh.position.z };
        let newP = {
            x: targetVector.x,
            y: targetVector.y,
            z: targetVector.z
        }
        time = defaultValue(time, 2000);

        if (mesh.usingTween) {
            mesh.usingTween.stop();
            mesh.usingTween._forceStop = true;
            mesh.usingTween._onCompleteCallback();
        }

        let tween = new TWEEN.Tween(oldP)
        tween.to(newP, time)
        //todo position如何转换到世界坐标？如果转换成世界，是否会影响到模型？
        tween.onUpdate(obj => {
            mesh.position.set(obj.x, obj.y, obj.z)
        })
        tween.onComplete(() => {
            mesh.usingTween = null;
            this.controller.enabled = true;
            callback && callback();
        });
        tween.easing(TWEEN.Easing.Cubic.InOut);
        this.controller.enabled = false;
        mesh.usingTween = tween;
        tween.start();
    };

    moveMesh(mesh, moveVector, time) {
        return new Promise((resolve, reject) => {
            this._moveMesh(mesh, moveVector, time, () => {
                resolve();
            });
        });
    };
}